package IC.AST.SemanticAnalysis;

import IC.AST.*;
import IC.Error.*;
import IC.Error.Error.ErrorType;
import IC.SymbolTables.SymbolTable;
import IC.SymbolTables.Types.ArrayType;
import IC.SymbolTables.Types.Symbol;
import IC.SymbolTables.Types.VoidType;
import IC.SymbolTables.Types.NullType;
import IC.SymbolTables.Types.Symbol.Kind;
import IC.SymbolTables.Types.MethodType;
import IC.SymbolTables.Types.InstanceType;
import IC.SymbolTables.Types.BasicType.BasicTypeEnum;
import IC.AST.BinaryOperationExpression.OperationType;

class SetAllTypesVisitor extends IC.AST.Visitor 
{
	private SymbolTable _lastSymTable = null;
	private boolean _isStatic;
	private String _classID, _functionID;

	protected Object VisitSons(ASTNode node)
	{
		for (ASTNode son : node.GetSons())
		{
			_lastSymTable = node.GetSymbolTable();
			son.Visit(this);
		}
		_lastSymTable = node.GetSymbolTable();
		return null;
	}
	
	public Object Visit(Program rootNode) { return VisitSons(rootNode); }
	
	public Object Visit(ClassDeclaration classDec) 
	{
		_classID = classDec.GetID();
		return VisitSons(classDec);
	}
	
	public Object Visit(Method method) 
	{
		_isStatic = method.IsStatic();
		_functionID = method.GetID();
		
		method.SetSymbolTable(new SymbolTable("Method Symbol Table: " + method.GetID(), method.GetID(), _lastSymTable));
		
		for(ASTNode son : method.GetFormals())
		{
			_lastSymTable = method.GetSymbolTable();
			son.Visit(this); //to add the formal to the symbol table
		}
		if(method.GetReturnType() != null)
			method.GetReturnType().Visit(this);
		
		_lastSymTable = method.GetSymbolTable();
		method.GetScope().Visit(this);
		
		return null;
	}

	private Object SetSymbolTable(ASTNode node)
	{
		node.SetSymbolTable(_lastSymTable);
		return null;
	}
	
	private Object SetSymbolTableAndVoidType(ASTNode node)
	{
		node.SetType(new VoidType());
		return SetSymbolTable(node);
	}
	
	public Object Visit(ClassType node) { return SetSymbolTable(node); }
	public Object Visit(BasicType node) { return SetSymbolTable(node); }

	public Object Visit(Formal formal)
	{
		SetSymbolTable(formal);
		VisitSons(formal);
		if(!_lastSymTable.SetSymbol(
				formal.GetID(), 
				new Symbol(Kind.FORMAL, formal.GetType(), _isStatic)))
		{
			return ErrorStack.Push(
					new ErrorStringIntervaled(ErrorType.Semantic, String.format("Can't redifine formal='%s' in function='%s' in class='%s'",
							formal.GetID(), _functionID, _classID), formal.GetInterval()));
		}
		return null;
	}
	
	public Object Visit(ScopeStatement node)
	{
		node.SetType(new VoidType());
		node.SetSymbolTable(
				new SymbolTable(
						String.format("Statement Block Symbol Table ( located in %s )", 
								_lastSymTable.GetShortName()),
						"Statement Block in " + _lastSymTable.GetShortName(),
						_lastSymTable));
		return VisitSons(node);
	}

	public Object Visit(FunctionCallStatement node)
	{
		SetSymbolTableAndVoidType(node);
		node.GetFunctionCall().Visit(this);
		return null; 
	}	
	
	public Object Visit(VirtualFunctionCall node)
	{
		SetSymbolTable(node);
		SymbolTable lookIn = null;

		node.GetInvokedExpression().Visit(this);			
		lookIn = CheckExpressionSymTable(node.GetInvokedExpression(), node.GetFunctionName());
		
		if(lookIn == null)
		{
			node.SetNotFound();
			node.SetType(new MethodType(new VoidType(), null));
		}
		
		Symbol classSym = (lookIn == null) 
				? 
					null 
				:
					_lastSymTable.GetSymbol(
						((IC.SymbolTables.Types.ClassType)node.GetInvokedExpression().GetType())
							.GetClassID(), false);
		
		return VisitFunction(node, classSym, lookIn, false);
	}
	
	private SymbolTable CheckExpressionSymTable(Expression exp, String functionName)
	{
		if (!(exp.GetType() instanceof IC.SymbolTables.Types.ClassType))
		{
			ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
					String.format("Can't invoke virtual function '%s' on an none class", functionName), 
					exp.GetInterval()));
			return null;
		}
			
		return ((IC.SymbolTables.Types.ClassType)exp.GetType()).GetSymbolTable();
	}
	
	public Object Visit(StaticFunctionCall node)
	{
		SetSymbolTable(node);
		
		Symbol classSym = _lastSymTable.GetSymbol(node.GetClassName(), false);
		if (classSym == null)
		{
			node.SetType(new NullType());
			node.SetNotFound();
			ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
					String.format("Can't find class declaration = '%s' to invoce static function = '%s'", 
							node.GetClassName(), node.GetFunctionName()), 
					node.GetInterval()));
		}
		
		SymbolTable classSymTable = (classSym == null)
					?
						null
					:
						((IC.SymbolTables.Types.ClassType)classSym.GetType()).GetSymbolTable();
		
		return VisitFunction(node, classSym, classSymTable, true);
	}
	
	private Object VisitFunction(FunctionCall function, Symbol classSym, SymbolTable lookIn, boolean isStatic)
	{
		VisitSons(function);
		
		if(lookIn == null)
			return null;
		
		Symbol funSymbol = lookIn.GetSymbol(function.GetFunctionName(), isStatic);
		
		if ((funSymbol == null) || !funSymbol.GetKind().equals(Kind.METHOD))
		{
			String errMessage;
			
			//C
			if ((funSymbol = lookIn.GetSymbol(function.GetFunctionName(), !isStatic)) != null)
			{
				function.SetType(funSymbol.GetType());
				errMessage = isStatic ? 
						"A virtual call to a static" : "A static call to a virtual";
			}
			else
			{
				function.SetType(new NullType());
				errMessage = "Call to undefined";
				function.SetNotFound();
			}
			return ErrorStack.Push(
					new ErrorStringIntervaled(
							ErrorType.Semantic, 
							String.format("%s function = '%s' of class '%s'", 
									errMessage, 
									function.GetFunctionName(), 
									((IC.SymbolTables.Types.ClassType)classSym.GetType()).GetClassID()), 
							function.GetInterval()));
		}
		
		function.SetType(funSymbol.GetType());
		
		return null; 
	}

	public Object Visit(BinaryOperationExpression node)
	{
		SetSymbolTable(node);
		VisitSons(node);
		
		OperationType operatorType = node.GetOperation().GetType(); 
		if (operatorType.equals(OperationType.Integer))
			node.SetType(new IC.SymbolTables.Types.BasicType(BasicTypeEnum.INT));
		else if (operatorType.equals(OperationType.Boolean))
			node.SetType(new IC.SymbolTables.Types.BasicType(BasicTypeEnum.BOOL));
		else if(operatorType.equals(OperationType.NotSet)) //for + expressions
			node.SetType(
						node.GetFirstExp().GetType().equals(new IC.SymbolTables.Types.BasicType(BasicTypeEnum.STRING)) 
						?
							new IC.SymbolTables.Types.BasicType(BasicTypeEnum.STRING)
						:
							new IC.SymbolTables.Types.BasicType(BasicTypeEnum.INT));
		return null;
	}
	
	public Object Visit(UnaryOperationExpression node)
	{
		SetSymbolTable(node);
		node.GetExpression().Visit(this);
		
		if (node.GetOperation().GetType().equals(UnaryOperationExpression.OperationType.Integer))
			node.SetType(
					new IC.SymbolTables.Types.BasicType(IC.SymbolTables.Types.BasicType.BasicTypeEnum.INT));
		else if (node.GetOperation().GetType().equals(UnaryOperationExpression.OperationType.Boolean))
			node.SetType(
					new IC.SymbolTables.Types.BasicType(IC.SymbolTables.Types.BasicType.BasicTypeEnum.BOOL));
		
		return null;
	}
	
	public Object Visit(FunctionCallExpression node)
	{
		SetSymbolTable(node);
		node.GetFunctionCall().Visit(this);

		if (!node.GetFunctionCall().WasFound())
			node.SetType(new NullType());
		else
			node.SetType(((MethodType)node.GetFunctionCall().GetType()).GetReturnType());
		
		return null;
	}
	
	public Object Visit(NewArrayExpression node)
	{
		SetSymbolTable(node);
		VisitSons(node);
		
		node.SetType(
				(node.GetASTType().GetType() instanceof InstanceType) ?
					new IC.SymbolTables.Types.ArrayType(
												(InstanceType)
													node.GetASTType().GetType())
				:
					new NullType());
		
		return null;
	}
	
	public Object Visit(NewClassExpression node)
	{
		SetSymbolTable(node);
		VisitSons(node);
		
		Symbol classSym = _lastSymTable.GetSymbol(node.GetClassID(), false);
		if (classSym == null)
		{
			node.SetType(new NullType());
			return ErrorStack.Push(
					new ErrorStringIntervaled(ErrorType.Semantic, 
							String.format("Can't create new object because the class name '%s' couldn't be resolved in function = '%s' in class = '%s'"
									, node.GetClassID(), _functionID, _classID), 
							node.GetInterval()));
		}
		
		node.SetType(classSym.GetType());
		return null;
	}
	
	public Object Visit(LiteralExpression node)
	{
		// type set in TrivialTypeVisitor.
		node.GetLiteral().Visit(this);
		return SetSymbolTable(node);
	}
	
	public Object Visit(LengthExpression node)
	{
		// type set in TrivialTypeVisitor.
		node.GetExpression().Visit(this);
		return SetSymbolTable(node);
	}
	
	public Object Visit(ThisExpression node)
	{
		SetSymbolTable(node);
		if (_isStatic)
			return ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
					String.format("Illegal usage of 'this' pointer, used in a static function = '%s'", _functionID), node.GetInterval()));
		
		node.SetType(_lastSymTable.GetSymbol(_classID, false).GetType());
		return null;
	}
	
	public Object Visit(DeclorationStatement node)
	{
		SetSymbolTableAndVoidType(node);
		VisitSons(node);

		if (_lastSymTable.SearchCurrentTable(node.GetID()) != null)
			return ErrorStack.Push(
					new ErrorStringIntervaled(
							ErrorType.Semantic, 
							String.format("Can't redeclare id='%s' in function='%s' in class='%s', since it has have been already declared in this scope",
									node.GetID(), _functionID, _classID),
							node.GetInterval()));
		
		Symbol lastDecl = _lastSymTable.GetSymbol(node.GetID(), _isStatic);
		if (lastDecl != null && lastDecl.GetKind().equals(Kind.FORMAL))
			return ErrorStack.Push(
					new ErrorStringIntervaled(
							ErrorType.Semantic, 
							String.format("Can't declare id='%s' in function='%s' in class='%s', since it hides a function parameter.",
									node.GetID(), _functionID, _classID),
							node.GetInterval()));
		
		_lastSymTable.SetSymbol(node.GetID(), new Symbol(Kind.VARIABLE, node.GetASTType().GetType(), _isStatic));
		return null;
	}
	
	public Object Visit(ContinueStatement node)	{ return SetSymbolTableAndVoidType(node); }
	public Object Visit(BreakStatement node) { return SetSymbolTableAndVoidType(node); }
	
	public Object Visit(AssignStatement node)
	{
		SetSymbolTableAndVoidType(node);
		return VisitSons(node);
	}
	
	public Object Visit(ReturnStatement node)
	{
		SetSymbolTable(node);
		if (node.GetReturnValue() != null)
		{
			node.GetReturnValue().Visit(this);
			node.SetType(node.GetReturnValue().GetType()); //used for checking return types to method return types.
		}
		else
			node.SetType(new VoidType());
		
		return null;
	}
	
	public Object Visit(WhileStatement node)
	{
		SetSymbolTableAndVoidType(node);
		return VisitSons(node);
	}
	
	public Object Visit(IfStatement node)
	{
		SetSymbolTableAndVoidType(node);
		return VisitSons(node);
	}
	
	public Object Visit(SimpleLocation node)
	{
		SetSymbolTable(node);
		
		Symbol lookup = LookUpID(node.GetID(), node);
		if (lookup != null)
			node.SetType(lookup.GetType());
		else
		{
			node.SetNotFound();
			node.SetType(new NullType());
		}
		return null;
	}
	
	private Symbol LookUpID(String id, SimpleLocation node)
	{
		Symbol lookup = _lastSymTable.GetSymbol(node.GetID(), _isStatic);
		if (lookup == null)
		{
			ErrorStack.Push(
					new ErrorStringIntervaled(
							ErrorType.Semantic, 
							String.format("Illegal usage of an undefined id='%s' in function='%s' in class='%s'", node.GetID(), _functionID, _classID), 
							node.GetInterval()));
			return null;
		}
		else if (lookup.GetKind().equals(Kind.METHOD))
		{
			ErrorStack.Push(
					new ErrorStringIntervaled(
							ErrorType.Semantic, 
							String.format("Illegal usage of an function id='%s' as identifier in function='%s' in class='%s'", 
									node.GetID(), _functionID, _classID), 
							node.GetInterval()));
			return null;
		}
		
		node.SetSymbolTable(node.GetSymbolTable().GetAppierenceTable(node.GetID(), _isStatic));
		return lookup;
	}
	
	public Object Visit(ArrayLocation node)
	{
		SetSymbolTable(node);
		VisitSons(node);
		
		IC.SymbolTables.Types.Type arrayType = node.GetArray().GetType();
		if (!(arrayType instanceof ArrayType))
		{
			node.SetNotFound();
			node.SetType(new NullType());
			return ErrorStack.Push(
					new ErrorStringIntervaled(
							ErrorType.Semantic, 
							String.format("Can't access array since expression is not of an array type. In function='%s', in class='%s'",_functionID, _classID),
							node.GetInterval()));
		}
		
		node.SetType(((ArrayType)arrayType).GetInerType());
		return null;
	}
	
	public Object Visit(DotLocation node) 
	{
		SetSymbolTable(node);
		VisitSons(node);
		
		if (!CheckIfLeftIsClassType(node))
		{
			node.SetType(new NullType());
			node.SetNotFound();
			return null;
		}
		
		Symbol lookUp = ((IC.SymbolTables.Types.ClassType)
							node.GetLeftExpression().GetType())
								.GetSymbolTable()
									.GetSymbol(
											node.GetRightID(),
											false);
		
		if (!IsAField(lookUp, node))
		{
			node.SetType(new NullType());
			node.SetNotFound();
		}
		else
			node.SetType(lookUp.GetType());
		
		return null; 
	}
	
	private boolean CheckIfLeftIsClassType(DotLocation node)
	{
		if (!(node.GetLeftExpression().GetType() instanceof IC.SymbolTables.Types.ClassType))
		{
			ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, String.format(
					"Can't resolve dot for an expression since it is not a class type. In function='%s' in class='%s'",
					_functionID, _classID), 
				node.GetInterval()));
			return false;
		}
		return true;
	}
	
	private boolean IsAField(Symbol lookUp, DotLocation node)
	{
		if (lookUp == null || lookUp.GetKind() != Kind.FIELD)
		{
			ErrorStack.Push(
					new ErrorStringIntervaled(
							ErrorType.Semantic, 
							String.format("Can't resolve dot for an expression since the '%s' field was !found. in function='%s' in class='%s'",
									node.GetRightID() ,_functionID, _classID), 
							node.GetInterval()));
			return false;
		}
		return true;
	}
	
	
	//Were set in trivial type checker.
	public Object Visit(BooleanLiteral node) { return SetSymbolTable(node); }
	public Object Visit(IntegerLiteral node) { return SetSymbolTable(node); }
	public Object Visit(QuoteLiteral node) { return SetSymbolTable(node); }
	public Object Visit(NullLiteral node) { return SetSymbolTable(node); }
	
	public Object Visit(ClassField node)
	{
		SetSymbolTable(node);
		return VisitSons(node);
	}
	
	public Object Visit(Library node)
	{
		_classID = "Library";
		_lastSymTable = node.GetSymbolTable();
		return VisitSons(node);
	}
	
	public Object Visit(LibraryMethod node)
	{
		_isStatic = true;
		_functionID = node.GetID();
		
		node.SetSymbolTable(new SymbolTable("Method Symbol Table: " + _functionID, _functionID, _lastSymTable));
		
		for(ASTNode son : node.GetFormals())
		{
			_lastSymTable = node.GetSymbolTable();
			son.Visit(this); //to add the formal to the symbol table
		}
		
		_lastSymTable = node.GetSymbolTable();
		return null;
	}
	
	public Object Visit(ASTNode node) { return null; }  //default
}
